#!/usr/bin/env python
#
# Name:     SPIURL
# Purpose:  Static Profile Image Urls for Twitter
# Author:   Shannon Whitley
# Created:  01/17/2009
# Version:  1.0
#
# Copyright 2009 Whitley Media
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# M O D I F I C A T I O N S
#
# 2/27/09 Joshua Fraser (joshfraser.com)
# - added content-type header (image/jpeg)
# - added support for both large and small image sizes
# 3/12/09 Shannon Whitley
# - Added support for mini, bigger, and original sizes.
# - Maintained 'large' size for compatibility with joshfraser.
# - Changed source for profile image link to Twitter Search
# - Avatar may not be available if protected tweets or no updates.
# - Added default avatar when avatar is not available.
# 3/19/09 Shannon Whitley
# - Added secondary call to the Twitter API.
# - Modified to add correct content-type.
# 7/26/2009 Shannon Whitley
# - Caching images and only updating once per day by request.
# - screen_name input is now case insensitive.

import wsgiref.handlers
import xml.dom.minidom

from google.appengine.ext import webapp

from google.appengine.api import urlfetch
from google.appengine.ext import db
from google.appengine.ext.db import *
from xml.dom.minidom import Node


class MainHandler(webapp.RequestHandler):

  def get(self, _screen_name='', _size=''):
	_size = _size.lower()
	#Size Input Conversion and Checks
	if _size == 'large':
		_size = 'original'
	if _size == '':
		_size = 'normal'
	if _size <> 'mini' and _size <> 'normal' and _size <> 'original' and _size <> 'bigger':
		_size = 'normal'
	_screen_name = _screen_name.replace("/","")
	if len(_screen_name) == 0:
		_screen_name = self.request.get("screen_name")
	_screen_name = _screen_name.lower()
	if len(_screen_name) == 0:
		self.response.out.write("<html><head><title>SPIURL</title><style type='text/css'>body {font-family:Arial}</style></head><body><div><h1>SPIURL</h1><p>-- Static Profile Image Urls for Twitter --</p>")
		self.response.out.write("<p>Please supply a Twitter screen_name.  Ex. http://purl.org/net/spiurl/swhitley</p>")
		self.response.out.write("<p>You may also request a size (mini, bigger, original, large).<br/>Ex. http://purl.org/net/spiurl/swhitley/mini</p>")
		self.response.out.write("<p>Author: Shannon Whitley - <a href=\"http://www.voiceoftech.com/swhitley\">http://www.voiceoftech.com/swhitley</a></p>")
		self.response.out.write("<p>Contributors:<br/>")
		self.response.out.write("Joshua Fraser - <a href=\"http://joshfraser.com\">http://joshfraser.com</a><br/>")
		self.response.out.write("Pete Warden - <a href=\"http://petewarden.typepad.com/\">http://petewarden.typepad.com/</a>")
		self.response.out.write("</p><p>Licensed under the Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0</p>")
		self.response.out.write("<p>This is an <a href='http://code.google.com/p/spiurl/'>Open Source</a> project.  Please download the code and create your own instance.</p>")
		self.response.out.write("</div></body></html>")
		return
	if len(_screen_name) > 15:
		self.response.out.write("Screen name cannot exceed 15 characters.")
		return
	_getImage = 0
	_url = ""
	_lastupdate = datetime.datetime.now()
	_lastfetch = datetime.datetime.now()	
	class Img(db.Model):
		screen_name 	= db.StringProperty()
		size			= db.StringProperty()
		url 			= db.StringProperty()
		image	 		= db.BlobProperty(default=None)
		content_type 	= db.StringProperty()
		lastupdate 		= db.DateTimeProperty()
		lastfetch  		= db.DateTimeProperty()	  
    #Get the url from the database.
	q = Img.gql("WHERE screen_name = :1 and size = :2 ", _screen_name, _size)
	cache_ok = 1
	for img in q:
		_url 			= img.url
		_image 			= img.image
		_content_type 	= img.content_type
		_lastupdate 	= img.lastupdate
		_lastfetch 		= img.lastfetch
		if _lastupdate == None:
			cache_ok = 0
			_lastupdate = datetime.datetime.now()
			_lastfetch = datetime.datetime.now()
			img.lastupdate=_lastupdate
			img.lastfetch=_lastfetch
			img.put()
	response = None
	min1 	= datetime.timedelta(minutes=1)
	hour1 	= datetime.timedelta(hours=1)
	day1 	= datetime.timedelta(days=1)
	if len(_url) > 0:
		#If daily fetch was good, return the cached image.
		if cache_ok == 1 and (_lastfetch + day1) > datetime.datetime.now():
			self.response.headers['Content-Type'] = str(_content_type)
			self.response.out.write(_image)
			return
		#Is the url still good?
		try:
			response = urlfetch.fetch(_url, payload=None, method=urlfetch.GET, headers={}, allow_truncated=False, follow_redirects=True)
			q = Img.gql("WHERE screen_name = :1 and size = :2 ", _screen_name, _size)
			#Bad url.
			if response.status_code <> 200:
				_getImage = 1
			#Default image is always good so have to check each day for an update.
			def_image = sizeIt(_size, "https://static.twitter.com/images/default_profile_normal.png")
			if _url == def_image  and (_lastupdate + day1) < datetime.datetime.now():
				_getImage = 1
		except:
			_getImage = 1
		if _getImage <> 1:
			#update last fetch
			for img in q:
				img.lastfetch = datetime.datetime.now()
				img.put()
	else:
		_url=""
		_getImage = 1
	if _getImage == 1:
		#Fetch the profile link from Twitter.
		try:
			q = Img.gql("WHERE screen_name = :1 and size = :2 ", _screen_name, _size)
			#Delete any old records.
			for img in q:
				img.delete()
			#Try Search API or Twitter API
			searchAPI = 1
			response = None
			try:
				response = urlfetch.fetch("http://search.twitter.com/search.atom?q=from%3A" + _screen_name + "&rpp=1", payload=None, method=urlfetch.GET, headers={}, allow_truncated=False, follow_redirects=False)
				loc = response.content.find("rel=\"image\"")
				if loc < 0:
					response = None
			except:
				response = None
			if response == None:
				searchAPI = 0
				response = urlfetch.fetch("http://twitter.com/users/show/" + _screen_name + ".xml", payload=None, method=urlfetch.GET, headers={}, allow_truncated=False, follow_redirects=False)
			#Load the XML & grab the profile image.
			doc = xml.dom.minidom.parseString(response.content)
			mapping = {}
			if searchAPI == 1:
				_url = getImage(doc.getElementsByTagName("link"))
			else:
				node = doc.getElementsByTagName("profile_image_url")[0]
				_url = getText(node.childNodes)
			_url = sizeIt(_size, _url)				
			response = urlfetch.fetch(_url, payload=None, method=urlfetch.GET, headers={}, allow_truncated=False, follow_redirects=True)				
			#Update the database with the new link and image.
			if response <> None:
				if response.headers['content-type'] <> 'application/xml':
					img = Img(screen_name=_screen_name,size=_size,url=_url,image=db.Blob(response.content),content_type=response.headers['content-type'],lastupdate=datetime.datetime.now(),lastfetch=datetime.datetime.now())
					img.put()
				else:
					_url = ""
			else:
				_url = ""
		except:
			_url = ""
			response = None
	if len(_url) == 0:
		#SPIURL default avatar
		_url = sizeIt(_size,"http://s3.amazonaws.com/twitter_production/profile_images/66883316/twitter_57_normal.png")
		self.redirect(_url)
	#Write the image.
	if response <> None:
		self.response.headers['Content-Type'] = response.headers['content-type']
		self.response.out.write(response.content)
	
def sizeIt(_size, _url):
	_temp_url = _url
	# mini, normal, bigger, large
	if _size == "mini":
		_temp_url = _url.replace("_normal","_mini")
	if _size == "bigger":
		_temp_url = _url.replace("_normal","_bigger")
	if _size == "original":
		_temp_url = _url.replace("_normal","")		
		
	return _temp_url


def getImage(nodelist):
	rc = ""
	for node in nodelist:
			if node.getAttribute("rel") == "image":
					rc = rc + node.getAttribute("href")
	return rc
	
def getText(nodelist):
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc	


def main():

	application = webapp.WSGIApplication([
		(r'^/', MainHandler),
		(r'^/(.*)/(.*)', MainHandler),
		(r'^/(.*)', MainHandler)])
				
	wsgiref.handlers.CGIHandler().run(application)


if __name__ == '__main__':
  main()
